﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Brio.Framework;

namespace Brio
{
    [Serializable]
    public class MotifChromosome : IComparable<MotifChromosome>
    {
        public MotifChromosome()
        {
            Staff = new Staff(Settings.Measures, Settings.Scale, Settings.TimeSignature, Settings.Tempo);
            Metrics = new MotifMetrics();
        }


        public Staff Staff
        { get; set; }

        public MotifMetrics Metrics
        { get; set; }


        public double ProportionalizedScaledFitness
        { get; set; }

        public double ScaledFitness
        { get; set; }

        public double Fitness
        { get; set; }


        public int CompareTo(MotifChromosome chromo)
        {
            // Compare fitnesses to sort
            return Fitness.CompareTo(chromo.Fitness);
        }

        public MotifChromosome Copy()
        {
            return HelperMethods.DeepClone<MotifChromosome>(this);
        }


        /// <summary>
        /// Clear fitness values for a new round
        /// </summary>
        public void DoPreFitness()
        {
            Fitness = 0.0;
            ScaledFitness = 0.0;
            ProportionalizedScaledFitness = 0.0;
        }

        public void DoFitness(TechniqueCollection collection, bool doFinalMetrics)
        {
            DoPreFitness();

            // DoBuildingBlockFitness()

            // Clear the metrics after every evaluation
            Metrics.NeighborMetrics.Clear();
            Metrics.TechniqueMetrics.Clear();

            // Data structure to understand how many points should be rewarded to each uniqueID
            Dictionary<int, int> buckets = new Dictionary<int, int>();
            // Data structure to understand how the techniques were laid out over time to assign
            // neighboring points
            List<Technique> timeline = new List<Technique>();

            // O(n)
            for (int i = 0; i < Staff.Length; i++)
            {
                int bucketID = Staff[i].UniqueTechniqueID;
                bool marked  = Staff[i].Marked;

                
                if (bucketID != -1)
                {
                    /////////////////////////////
                    // Step 1:                 //
                    // Add to unique ID Bucket //
                    /////////////////////////////

                    // Add to bucket to buckets if it isn't there
                    if (!buckets.ContainsKey(bucketID))
                    {
                        buckets.Add(bucketID, 0);
                    }

                    // If there were no altercations increase that bucket count
                    if (!marked)
                    {
                        buckets[bucketID]++;
                    }


                    /////////////////////////////
                    // Step 2:                 //
                    // Add to timeline list if //
                    // it is a new technique   //
                    /////////////////////////////

                    // Lookup uniqueID and get technique Name
                    string techniqueKey = Staff.TechniqueLookupTable[Staff[i].UniqueTechniqueID].Item1;

                    Technique technique = collection[techniqueKey];

                    // If this is the first technique then add it to the timeline
                    if (timeline.Count == 0)
                    {
                        timeline.Add(technique);
                    }
                    // If this is a new technique then add it to the timeline
                    else if (!timeline.Last().Name.Equals(technique.Name, StringComparison.InvariantCultureIgnoreCase))
                    {
                        timeline.Add(technique);
                    }


                }


            }


            /////////////////////////////
            // Step 3:                 //
            // count instances of the  //
            // same technique and      //
            // perform exp. decay func //
            // rewards for fitness     //
            /////////////////////////////
            double points = 0;

            // O(n)
            for (int i = 0; i < buckets.Count; i++)
            {
                // get the unique key of the bucket
                string techniqueKey = Staff.TechniqueLookupTable[buckets.ElementAt(i).Key].Item1;

                // use the unique key to look up the technique.
                Technique technique = collection[techniqueKey];

                if (!Metrics.TechniqueMetrics.ContainsKey(technique.Name))
                {
                    Metrics.TechniqueMetrics.Add(technique.Name, new MotifMetricTechnique(technique));
                    Metrics.TechniqueMetrics[technique.Name].NumberOfOccurances++;
                }
                else
                {
                    Metrics.TechniqueMetrics[technique.Name].NumberOfOccurances++;
                }

                double observedUnmarked = buckets.ElementAt(i).Value;
                double totalUnmarked = technique.Length;

                double ratio = observedUnmarked / totalUnmarked;

                Metrics.TechniqueMetrics[technique.Name].AverageRatio += ratio;

                double weighting = Math.Log10(10.0 / Settings.ExploratoryRatio * ratio);

                weighting = Math.Min(1.0, Math.Max(0.0, weighting));

                double expdecay = Math.Pow(Math.E, -technique.DecayRate * Metrics.TechniqueMetrics[technique.Name].NumberOfOccurances);

                double techniquePoints = weighting * technique.Value * expdecay;


                Metrics.TechniqueMetrics[technique.Name].TotalPoints += techniquePoints;

                //////////////////////////////////////////////////////
                // http://en.wikipedia.org/wiki/Exponential_decay   //
                // ratio * technique.Value = Initial Value          //
                // expDecay = e^(-rate * (# of instances))          //
                //////////////////////////////////////////////////////
                points += techniquePoints;
            }

            if (doFinalMetrics)
            {
                // Do Average Point Value and Ratio
                for (int i = 0; i < Metrics.TechniqueMetrics.Count; i++)
                {
                    MotifMetricTechnique metricTechnique = Metrics.TechniqueMetrics.ElementAt(i).Value;

                    metricTechnique.AveragePointValue = metricTechnique.TotalPoints / metricTechnique.NumberOfOccurances;
                    metricTechnique.AverageRatio /= metricTechnique.NumberOfOccurances;
                }
            }

            /////////////////////////////
            // Step 4:                 //
            // Reward any techniques   //
            // that have adjancy       //
            // bonuses                 //
            /////////////////////////////

            // O(n)
            for (int i = 0; i < timeline.Count - 1; i++)
            {
                Technique currTechnique = timeline[i];
                Technique nextTechnique = timeline[i + 1];

                // TODO: Add LHS / RHS relationships and multiple relationships
                // so that i say foreach(string neighbor in currTechnique.Neighbors)
                if (currTechnique.Neighbor.Equals(nextTechnique.Name, StringComparison.InvariantCultureIgnoreCase))
                {
                    if (!Metrics.NeighborMetrics.ContainsKey(currTechnique.Name))
                    {
                        Metrics.NeighborMetrics.Add(currTechnique.Name, new MotifMetricNeighbor(currTechnique, nextTechnique));
                    }
                    else
                    {
                        Metrics.NeighborMetrics[currTechnique.Name].NumberOfOccurances++;
                    }

                    // We have a datatype called countbuckets which has each technique and the amount of times it appears.

                    double expLHSdecay = Math.Pow(Math.E, -currTechnique.DecayRate * Metrics.TechniqueMetrics[currTechnique.Name].NumberOfOccurances);
                    double lhsPointValue = currTechnique.Value;

                    double expRHSdecay = Math.Pow(Math.E, -nextTechnique.DecayRate * Metrics.TechniqueMetrics[nextTechnique.Name].NumberOfOccurances);
                    double rhsPointValue = nextTechnique.Value;

                    double neighborMultiplier = currTechnique.NeighborMultiplier;


                    // Our formula for neighborhood bonuses is as follows
                    // (Exp. Decayed Point Value of LHS + Exp. Decayed Point Value of RHS) * (Neighborhood Bonus Multiplier)
                    double neighborBonus = ((expLHSdecay * lhsPointValue) + (expRHSdecay * rhsPointValue)) * neighborMultiplier;

                    Metrics.NeighborMetrics[currTechnique.Name].TotalPoints += neighborBonus;

                    points += neighborBonus;
                }

            }

            if (doFinalMetrics)
            {
                // Do Average Neighborhood Value and Ratio
                for (int i = 0; i < Metrics.NeighborMetrics.Count; i++)
                {
                    Metrics.NeighborMetrics.ElementAt(i).Value.AveragePointValue = Metrics.NeighborMetrics.ElementAt(i).Value.TotalPoints /
                                                                                    Metrics.NeighborMetrics.ElementAt(i).Value.NumberOfOccurances;
                }
            }

            Fitness = points;
        }


        public int DoBuildingBlockFitness()
        {
            // Explanation : The simple fitness function I wish to achieve is
            // A measure of music that every music note is dotted eighth notes
            // This means the GA will have to split and join notes if it hopes to achieve the maximize fitness
            // This will be a good way to show that the GA is capable of the task at hand, if not- rethink

            int divFactor = Constants.RHYTHM_WHOLE_VALUE / Constants.SMALLEST_RHYTHMIC_UNIT;
            int noteLengthExpBB2 = (8) / divFactor; // 8 / 4
            int noteLengthExpBB1 = (16) / divFactor; // 16 / 4
            int BB1_Value = 4;
            int BB2_Value = 8;

            // 32 was a measure

            int count = 0;


            count += BB1_Value * Staff.Rhythm.Count(amount => amount == noteLengthExpBB1);

            count += BB2_Value * Staff.Rhythm.Count(amount => amount == BB1_Value);

            return count;
        }



       
    }
}
